import _ from "underscore";
import { failJson } from "../utils/fail";
import { getConversationInfo } from "../conversation";
import { getUser } from "../user";
import { getXidRecord } from "../xids";
import { isPolisDev } from "../utils/common";
import { sql_users } from "../db/sql";
import pg from "../db/pg-query";
import {
  generateSUZinvites,
  saveSuzinvites,
  sendSuzinviteEmail,
} from "../invites/suzinvites";
import type {
  UserType,
  ExpressRequest,
  ExpressResponse,
  ConversationInfo,
} from "../d";

// Types for better type safety
interface GetUsersRequest extends ExpressRequest {
  p: {
    uid?: number;
    xid: string;
    owner_uid?: number;
  };
}

interface PutUsersRequest extends ExpressRequest {
  p: {
    uid?: number;
    uid_of_user?: number;
    email?: string;
    hname?: string;
  };
}

export interface PostUsersInviteRequest extends ExpressRequest {
  p: {
    uid?: number;
    emails: string[];
    zid: number;
    conversation_id: string;
  };
}

interface StandardResponse extends ExpressResponse {
  status: (code: number) => { json: (data: any) => void };
}

async function handle_GET_users(
  req: GetUsersRequest,
  res: StandardResponse
): Promise<void> {
  let { uid } = req.p;
  const { xid, owner_uid } = req.p;

  if (!uid) {
    failJson(res, 401, "Authentication required");
    return;
  }

  try {
    // If looking up by XID, first get the XID user's uid
    if (xid && owner_uid) {
      const xidRecords = await getXidRecord(xid, undefined, owner_uid);
      if (xidRecords && xidRecords.length > 0) {
        uid = xidRecords[0].uid;
      }
    }

    const user = await getUser(uid, null, xid, owner_uid);
    res.status(200).json(user);
  } catch (error) {
    failJson(res, 500, "polis_err_getting_user_info", error);
  }
}

async function handle_PUT_users(
  req: PutUsersRequest,
  res: StandardResponse
): Promise<void> {
  let { uid } = req.p;
  const { uid_of_user, email, hname } = req.p;

  // Allow polis dev to update other users
  if (isPolisDev(uid) && uid_of_user) {
    uid = uid_of_user;
  }

  if (!uid) {
    failJson(res, 403, "Authentication required");
    return;
  }

  const fields: UserType = {};
  if (email !== undefined) {
    fields.email = email;
  }
  if (hname !== undefined) {
    fields.hname = hname;
  }

  try {
    const query = sql_users.update(fields).where(sql_users.uid.equals(uid));
    const result = await pg.queryP(query.toString(), []);
    res.status(200).json(result);
  } catch (error) {
    failJson(res, 500, "polis_err_put_user", error);
  }
}

// Helper function to record inviter relationship
async function addInviter(
  inviter_uid: number,
  invited_email: string
): Promise<void> {
  await pg.queryP(
    "INSERT INTO inviters (inviter_uid, invited_email) VALUES ($1, $2)",
    [inviter_uid, invited_email]
  );
}

async function handle_POST_users_invite(
  req: PostUsersInviteRequest,
  res: StandardResponse
): Promise<void> {
  const { uid, emails, zid, conversation_id } = req.p;

  if (!uid) {
    failJson(res, 403, "Authentication required");
    return;
  }

  try {
    // Get conversation info
    const conversation: ConversationInfo = await getConversationInfo(zid);
    const { owner } = conversation;

    // Generate invitation tokens
    const suzinvites = generateSUZinvites(emails.length);

    // Save invites to database
    await saveSuzinvites(emails, suzinvites, zid, owner);

    // Send emails and record inviter relationships
    const emailPromises = emails.map(async (email, index) => {
      const suzinvite = suzinvites[index];

      try {
        await sendSuzinviteEmail(req, email, conversation_id, suzinvite);
        await addInviter(uid, email);
      } catch (error) {
        throw new Error(`Failed to send invite to ${email}: ${error}`);
      }
    });

    await Promise.all(emailPromises);

    res.status(200).json({
      status: "success",
    });
  } catch (error) {
    // Determine appropriate error message based on the step that failed
    let errorCode = "polis_err_sending_invite";

    if (error instanceof Error) {
      if (error.message.includes("conversation")) {
        errorCode = "polis_err_getting_conversation_info";
      } else if (error.message.includes("saving")) {
        errorCode = "polis_err_saving_invites";
      } else if (error.message.includes("generating")) {
        errorCode = "polis_err_generating_invites";
      }
    }

    failJson(res, 500, errorCode, error);
  }
}

export { handle_GET_users, handle_PUT_users, handle_POST_users_invite };
